package org.tp8.sampleapp.loader;

import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.tp8.sampleapp.loader.exc.SampleLoaderException;
import org.tp8.sampleapp.loader.util.LoaderConstants;
import org.tp8.sampleapp.loader.util.SampleLoaderLogger;

/**
 * The Class Executor.
 */
@Component
public class Executor {

	/** The log. */
	private static SampleLoaderLogger log = new SampleLoaderLogger(
			Executor.class);

	/** The spring app context. */
	@Autowired
	private ApplicationContext appContext;

	/** The max threads. */
	@Value("${max.threads}")
	private int maxThreads;

	/** The finished processing. */
	private boolean finishedProcessing;

	/** The application context. */
	@Resource
	private ApplicationContext applicationContext;

	/** The documents path. */
	@Value("${content.document.path}")
	private String documentsPath;

	/** The job name. */
	private String jobName;
	

	/**
	 * Run.
	 * 
	 * @param args
	 *            the args
	 * @throws SampleLoaderException
	 *             the sample loader exception
	 */
	public void run(String args[]) throws SampleLoaderException {

		extractArguments(args);

		long startTime = System.currentTimeMillis();

		runJobs(jobName);

		long duration = System.currentTimeMillis() - startTime;

		log.info("Duration (secs): " + (duration / 1000));

	}

	/**
	 * this method extract the arguments from the command line. it passes on a
	 * specific order the migration type, the case type and the track source
	 * case
	 * 
	 * @param args
	 *            the args
	 * @throws SampleLoaderException
	 *             the cMT exception
	 */
	private void extractArguments(String[] args) throws SampleLoaderException {

		jobName = args[0];

	}

	/**
	 * the method runJobs gets an instance of the spring batch It add the jobs
	 * in a list and checks continuously for the jobs state Also it apply
	 * information for each new job (id and start time) until there are lunched
	 * jobs.
	 * 
	 * @param jobName
	 *            the job name
	 * @throws SampleLoaderException
	 *             the cMT exception
	 */
	private void runJobs(String jobName) throws SampleLoaderException {
		JobLauncher jobLauncher = appContext.getBean("asyncJobLauncher",
				JobLauncher.class);
		Set<JobExecution> jobs = new HashSet<JobExecution>();

		String jobId;

		// Start threads
		int count = 0;
		boolean launchJobs = true;
		while (launchJobs) {
			count++;
			while (jobs.size() >= maxThreads) {
				launchJobs = checkJobs(jobs);
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
				}
			}
			Job job = appContext.getBean(jobName, Job.class);

			JobParametersBuilder builder = new JobParametersBuilder();
			jobId = jobName + "-" + count;
			builder.addString("job-id", jobId);
			builder.addDate("job-start", new Date());
			JobParameters jobParams = builder.toJobParameters();
			try {
				JobExecution jobExecution = jobLauncher.run(job, jobParams);
				jobs.add(jobExecution);
				log.info("Started: " + jobId);
			} catch (Exception e) {
				log.error(e);
			}

		}
		// Wait for processor threads to finish processing cases
		waitThreads(jobs);
		setFinishedProcessing(true);

		log.info("Loader finished.");
	}

	/**
	 * Wait threads checks if a job is running and wait for its completion.
	 * 
	 * @param jobs
	 *            the jobs
	 */
	private void waitThreads(Collection<JobExecution> jobs) {
		// Wait for threads to execute
		boolean processing = true;
		while (processing) {
			processing = false;
			for (JobExecution jobExecution : jobs) {
				if (jobExecution.isRunning()) {
					processing = true;
					try {
						Thread.sleep(1000L);
					} catch (InterruptedException e) {
					}
					break;
				}
			}
		}
	}

	/**
	 * The method checkJobs checks and removes all the finished jobs.
	 * 
	 * @param jobs
	 *            the jobs
	 * @return true, if successful
	 */
	private boolean checkJobs(Collection<JobExecution> jobs) {
		boolean continueExecution = true;
		Set<JobExecution> toRemove = new HashSet<JobExecution>();
		List<Long> idsToRemove = new LinkedList<Long>();

		for (JobExecution job : jobs) {
			if (LoaderConstants.FINISHED_ALL.equals(job.getExitStatus()
					.getExitCode())) {
				continueExecution = false;
			}
			if (!job.isRunning()) {
				toRemove.add(job);
				idsToRemove.add(job.getId());
			}
		}
		jobs.removeAll(toRemove);

		return continueExecution;
	}

	/**
	 * Checks if is finished processing.
	 * 
	 * @return true, if is finished processing
	 */
	public synchronized boolean isFinishedProcessing() {
		return finishedProcessing;
	}

	/**
	 * Sets the finished processing.
	 * 
	 * @param finishedProcessing
	 *            the new finished processing
	 */
	public synchronized void setFinishedProcessing(boolean finishedProcessing) {
		this.finishedProcessing = finishedProcessing;
	}

}
